﻿using System.Security.Cryptography;
using ZGDisassembler.Core;
using ZGDisassembler.UI.Editor;

namespace ZGDisassembler.UI;

public partial class Main : Form
{
	private readonly static string DefaultConfig = Path.Combine(AppContext.BaseDirectory, Config.ConfigFileName);
	private readonly static string ConfigRoot = Path.Combine(AppContext.BaseDirectory, "config");

	private readonly Disassembler disassembler = new();
	private string binaryFileMD5;
	private string binaryFilePath;
	private EditorForm editorForm;
	private System.Windows.Forms.Timer timer;

	public Main()
	{
		InitializeComponent();
		InitEvent();
		ControlEvent();
	}

	#region 初始化
	private void InitEvent()
	{
		CreateBaseDir();

		editorForm = new EditorForm();
		editorForm.Show(this);
		editorForm.GetLineNumber = GetLineNumber;
		editorForm.GetResultLine = GetResultLine;

		status_label.Text = "";
		status_label.ForeColor = Color.Red;
		timer = new() { Interval = 3 * 1000 };
		timer.Tick += delegate { status_label.Text = ""; timer.Stop(); };

		foreach (var p in disassembler.GetAllSupportPlatform)
			platform_list.Items.Add(p);

		var configPath = Path.Combine(AppContext.BaseDirectory, Config.ConfigFileName);
		disassembler.AppEnv.AppConfig.ReadConfig(configPath);

		ReadConfig();
		disassembler.Initialize();
	}
	#endregion 初始化

	#region 控件事件绑定
	private void ControlEvent()
	{
		platform_list.SelectedIndexChanged += Platform_list_SelectedIndexChanged;

		openDefaultConfig_btn.Click += OpenDefaultConfig_btn_Click;
		saveDefaultConfig_btn.Click += SaveDefaultConfig_btn_Click;

		openBinFileConfig_btn.Click += OpenBinFileConfig_btn_Click;
		saveFileConfig_btn.Click += SaveFileConfig_btn_Click;

		labelConfig_btn.Click += LabelConfig_btn_Click;

		readBinary_btn.Click += ReadBinary_btn_Click;
		decodeBin_btn.Click += DecodeBin_btn_Click;

		decodeHex_btn.Click += DecodeHex_btn_Click;

		editorForm.ChangeLineType = new Action(ChangeLineType);

		about_btn.Click += delegate { new About().ShowDialog(this); };
	}
	#endregion 控件事件绑定

	/***** 控件事件 *****/

	#region 改变平台
	private void Platform_list_SelectedIndexChanged(object? sender, EventArgs e)
	{
		if (platform_list.SelectedItem == null)
			return;

		var platform = platform_list.SelectedItem as string;
		disassembler.AppEnv.AppConfig.Platform = platform;
		disassembler.Initialize();
		ReadConfig();
	}
	#endregion 改变平台

	#region 打开默认配置文件
	private void OpenDefaultConfig_btn_Click(object? sender, EventArgs e)
	{
		Utils.GetFileLocation(DefaultConfig);
	}
	#endregion 打开默认配置文件

	#region 保存默认配置
	private void SaveDefaultConfig_btn_Click(object? sender, EventArgs e)
	{
		disassembler.AppEnv.AppConfig.SaveConfig(DefaultConfig);
		ShowText("已保存默认文件");
	}
	#endregion 保存默认配置

	#region 打开单个文件配置文件
	private void OpenBinFileConfig_btn_Click(object? sender, EventArgs e)
	{
		if (binaryFileMD5 == null)
			return;

		var path = Path.Combine(ConfigRoot, $"{binaryFileMD5}.json");
		Utils.GetFileLocation(path);
	}
	#endregion 打开单个文件配置文件

	#region 保存单个文件配置
	private void SaveFileConfig_btn_Click(object? sender, EventArgs e)
	{
		if (binaryFilePath == null)
			return;

		if (!Directory.Exists(ConfigRoot))
			Directory.CreateDirectory(ConfigRoot);

		var filePath = Path.Combine(ConfigRoot, $"{binaryFileMD5}.json");
		SetConfig();
		disassembler.AppEnv.AppConfig.SaveConfig(filePath);
		ShowText("已保存配置文件");
	}
	#endregion 保存单个文件配置

	#region 设置标签文件
	private void LabelConfig_btn_Click(object? sender, EventArgs e)
	{
		var form = new LabelLinks();
		form.SetFiles(disassembler.AppEnv.AppConfig.ReplaceLabelConfig);
		form.ShowDialog(this);
		if (!form.OK)
			return;

		disassembler.AppEnv.AppConfig.ReplaceLabelConfig = form.FileSets.ToArray();
	}
	#endregion 设置标签文件

	#region 读取二进制文件
	private void ReadBinary_btn_Click(object? sender, EventArgs e)
	{
		var ofd = new OpenFileDialog();
		ofd.FileOk += delegate
		{
			binaryFilePath = ofd.FileName;
			disassembler.LoadFile(ofd.FileName, 0);
			ShowText($"打开文件 \"{ofd.FileName}\"");

			using var stream = File.OpenRead(ofd.FileName);
			var md5 = MD5.Create().ComputeHash(stream);
			binaryFileMD5 = BitConverter.ToString(md5).Replace("-", "").ToLowerInvariant();

			ReadBinaryFileConfig();
		};
		ofd.ShowDialog(this);
	}
	#endregion 读取二进制文件

	#region 解码文件
	/// <summary>
	/// 解码文件
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	private void DecodeBin_btn_Click(object? sender, EventArgs e)
	{
		if (platform_list.SelectedIndex < 0)
		{
			MessageBox.Show("请选择解析平台", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
			return;
		}

		SetConfig();

		if (disassembler.AppEnv.FileDataLength == 0)
		{
			MessageBox.Show("请打开文件", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
			return;
		}

		if (!CheckNumberTextbox(orgAddress_text, "编译起始地址有误", out var orgAddress))
			return;

		if (!CheckNumberTextbox(fileStart_text, "文件起始位置有误", out var fileStart))
			return;

		disassembler.AppEnv.BaseAddress = fileStart!.Value;
		if (string.IsNullOrWhiteSpace(fileLength_text.Text))
			disassembler.AppEnv.FileReadLength = disassembler.AppEnv.FileDataLength;
		else if (CheckNumberTextbox(fileLength_text, "文件读取长度有误", out var temp))
			disassembler.AppEnv.FileReadLength = temp!.Value;
		else
			return;

		disassembler.DecodeDatas(orgAddress!.Value);
		var result = disassembler.OutputResult();
		editorForm.SetText(result);
	}
	#endregion 解码文件

	#region 解码16进制字符串
	private void DecodeHex_btn_Click(object? sender, EventArgs e)
	{
		if (platform_list.SelectedIndex < 0)
		{
			MessageBox.Show("请选择解析平台", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
			return;
		}

		if (!CheckNumberTextbox(orgAddress_text, "编译起始地址有误", out var orgAddress))
			return;

		SetConfig();

		var hexString = hexString_text.Text;
		disassembler.LoadHexString(hexString);
		disassembler.DecodeDatas(orgAddress!.Value);
		var result = disassembler.OutputResult();
		editorForm.SetText(result);
	}
	#endregion 解码16进制字符串

	#region 获取行号
	private string GetLineNumber(int lineNumber)
	{
		if (!disassembler.AppEnv.ResultLineNumberMap.TryGetValue((uint)lineNumber, out var address))
			return null;

		return "0x" + address.OrgAddress.ToString("X");
	}
	#endregion 获取行号

	#region 获取结果行
	private ResultLine GetResultLine(int lineNumber)
	{
		var env = disassembler.AppEnv;
		if (!env.ResultLineNumberMap.TryGetValue((uint)lineNumber, out var address))
			return null;

		if (!env.AllResult.TryGetValue(address.OrgAddress, out var resultLine))
			return null;

		return resultLine;
	}
	#endregion 获取结果行

	/***** Private *****/

	#region 改变行类型
	private void ChangeLineType()
	{
		var result = disassembler.OutputResult();
		editorForm.SetText(result);
	}
	#endregion 改变行类型

	#region 创建基础目录
	private static void CreateBaseDir()
	{
		var path = Path.Combine(AppContext.BaseDirectory, "labels");
		if (!Directory.Exists(path))
			Directory.CreateDirectory(path);

		if (!Directory.Exists(ConfigRoot))
			Directory.CreateDirectory(ConfigRoot);
	}
	#endregion 创建基础目录

	#region 显示提示并过段时间隐藏
	private void ShowText(string message)
	{
		if (string.IsNullOrWhiteSpace(message))
			return;

		timer.Stop();
		timer.Start();
		status_label.Text = message;
	}
	#endregion 显示提示并过段时间隐藏

	#region 检查TextBox数值是否正确
	private bool CheckNumberTextbox(TextBox box, string message, out uint? resultValue)
	{
		resultValue = Utils.CheckNumber(box.Text);
		if (resultValue == null)
		{
			ShowText(message);
			return false;
		}
		return true;
	}
	#endregion 检查TextBox数值是否正确

	#region 读取/设置配置文件
	private void ReadConfig()
	{
		var config = disassembler.AppEnv.AppConfig;

		platform_list.SelectedItem = disassembler.AppEnv.AppConfig.Platform;

		orgAddress_text.Text = GetValue(config.OrgAddress);
		label_text.Text = config.LabelName;
		labelIndex_text.Text = GetValue(config.LabelStartIndex, false);

		label_check.Checked = config.AbleLabel;
		orgLabel_check.Checked = config.ReplaceLabel;

		fileStart_text.Text = GetValue(config.BaseAddress);
		fileLength_text.Text = GetValue(config.ReadDataLength, false);
	}

	private void SetConfig()
	{
		var config = disassembler.AppEnv.AppConfig;

		config.OrgAddress = Utils.CheckNumber(orgAddress_text.Text);
		config.LabelName = label_text.Text;
		config.LabelStartIndex = Utils.CheckNumber(labelIndex_text.Text);

		config.AbleLabel = label_check.Checked;
		config.ReplaceLabel = orgLabel_check.Checked;

		uint? result = Utils.CheckNumber(fileStart_text.Text);
		if (result != null)
			config.BaseAddress = result;

		result = Utils.CheckNumber(fileLength_text.Text);
		if (result != null)
			config.ReadDataLength = result;
	}

	private static string GetValue(uint? value, bool hex = true)
	{
		if (value == null)
			return "";

		return hex ? $"0x{value.Value:X}" : value.Value.ToString();
	}
	#endregion 读取/设置配置文件

	#region 读取文件配置文件
	private void ReadBinaryFileConfig()
	{
		if (binaryFileMD5 == null)
			return;

		var path = Path.Combine(AppContext.BaseDirectory, "config", $"{binaryFileMD5}.json");
		disassembler.Initialize(path);
		ReadConfig();
	}
	#endregion 读取文件配置文件

}
